﻿using System;
using JetBrains.Annotations;
using MugenMvvmToolkit.Annotations;
using MugenMvvmToolkit.Attributes;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;

namespace MugenMvvmToolkit.ViewModels
{
    /// <summary>
    ///     Represents the async implementation of <see cref="IRelayCommand" />.
    /// </summary>
    public class AsyncRelayCommand : RelayCommand
    {
        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="RelayCommand" /> class.
        /// </summary>
        /// <param name="execute">The specified command action for execute.</param>
        /// <param name="executionMode">
        ///     Specifies the execution mode for <c>RaiseCanExecuteChanged</c> method.
        /// </param>
        /// <param name="threadManager">
        ///     The specified <see cref="IThreadManager" />.
        /// </param>
        public AsyncRelayCommand([NotNull]Action<object> execute, ExecutionType? executionMode = null,
                                 IThreadManager threadManager = null)
            : base(execute, executionMode, threadManager)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="RelayCommand" /> class.
        /// </summary>
        /// <param name="execute">The specified command action for execute.</param>
        /// <param name="canExecute">The specified command condition.</param>
        /// <param name="notifiers">
        ///     The specified object implemented for auto call event CanExecuteChanged. If you change the properties of the objects to be caused by an event CanExecuteChanged.
        /// </param>
        public AsyncRelayCommand([NotNull]Action<object> execute, [NotNull] Predicate<object> canExecute, [NotEmptyParams]  params object[] notifiers)
            : base(execute, canExecute, notifiers)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="RelayCommand" /> class.
        /// </summary>
        /// <param name="execute">The specified command action for execute.</param>
        /// <param name="canExecute">The specified command condition.</param>
        /// <param name="executionMode">
        ///     Specifies the execution mode for <c>RaiseCanExecuteChanged</c> method.
        /// </param>
        /// <param name="threadManager">
        ///     The specified <see cref="IThreadManager" />.
        /// </param>
        /// <param name="notifiers">
        ///     The specified object implemented for auto call event CanExecuteChanged. If you change the properties of the objects to be caused by an event CanExecuteChanged.
        /// </param>
        public AsyncRelayCommand([NotNull]Action<object> execute, [CanBeNull] Predicate<object> canExecute,
                                 ExecutionType? executionMode = null, IThreadManager threadManager = null,
                                 [NotEmptyParams] params object[] notifiers)
            : base(execute, canExecute, executionMode, threadManager, notifiers)
        {
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets or sets an action which will execute after invoke command method.
        /// </summary>
        public Action<object> Callback { get; set; }

        /// <summary>
        ///     Specifies the execution mode for <see cref="Callback" />.
        /// </summary>
        public ExecutionType CallbackExecutionMode { get; set; }

        #endregion

        #region Overrides of RelayCommand

        /// <summary>
        ///     Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
        public override void Execute(object parameter)
        {
            ThreadManager.InvokeAsync(o =>
                {
                    base.Execute(o);
                    Action<object> callback = Callback;
                    if (callback != null)
                        CallbackExecutionMode.Invoke(ThreadManager, () => callback(o));
                }, parameter);
        }

        #endregion
    }

    /// <summary>
    ///     Represents the async implementation of <see cref="IRelayCommand" />.
    /// </summary>
    public class AsyncRelayCommand<TArg> : AsyncRelayCommand
    {
        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="RelayCommand" /> class.
        /// </summary>
        /// <param name="execute">The specified command action for execute.</param>
        /// <param name="executionMode">
        ///     Specifies the execution mode for <c>RaiseCanExecuteChanged</c> method.
        /// </param>
        /// <param name="threadManager">
        ///     The specified <see cref="IThreadManager" />.
        /// </param>
        public AsyncRelayCommand([NotNull]Action<TArg> execute, ExecutionType? executionMode = null,
                                 IThreadManager threadManager = null)
            : base(o => execute((TArg)o), executionMode, threadManager)
        {
            Should.NotBeNull(execute, "execute");
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="RelayCommand" /> class.
        /// </summary>
        /// <param name="execute">The specified command action for execute.</param>
        /// <param name="canExecute">The specified command condition.</param>
        /// <param name="notifiers">
        ///     The specified object implemented for auto call event CanExecuteChanged. If you change the properties of the objects to be caused by an event CanExecuteChanged.
        /// </param>
        public AsyncRelayCommand([NotNull]Action<TArg> execute, [NotNull] Predicate<TArg> canExecute, [NotEmptyParams] params object[] notifiers)
            : base(o => execute((TArg)o), o => canExecute((TArg)o), notifiers)
        {
            Should.NotBeNull(execute, "execute");
            Should.NotBeNull(canExecute, "canExecute");
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="RelayCommand" /> class.
        /// </summary>
        /// <param name="execute">The specified command action for execute.</param>
        /// <param name="canExecute">The specified command condition.</param>
        /// <param name="executionMode">
        ///     Specifies the execution mode for <c>RaiseCanExecuteChanged</c> method.
        /// </param>
        /// <param name="threadManager">
        ///     The specified <see cref="IThreadManager" />.
        /// </param>
        /// <param name="notifiers">
        ///     The specified object implemented for auto call event CanExecuteChanged. If you change the properties of the objects to be caused by an event CanExecuteChanged.
        /// </param>
        public AsyncRelayCommand([NotNull]Action<TArg> execute, [NotNull] Predicate<TArg> canExecute,
                                 ExecutionType? executionMode = null, IThreadManager threadManager = null,
                                 [NotEmptyParams]params object[] notifiers)
            : base(o => execute((TArg)o), o => canExecute((TArg)o), executionMode, threadManager, notifiers)
        {
            Should.NotBeNull(execute, "execute");
            Should.NotBeNull(canExecute, "canExecute");
        }

        #endregion
    }
}